// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkGLSLModPixelDebugger.h"
#include "vtkActor.h"
#include "vtkCellGridMapper.h"
#include "vtkDrawTexturedElements.h"
#include "vtkInformation.h"
#include "vtkInformationObjectBaseKey.h"
#include "vtkLightingMapPass.h"
#include "vtkLogger.h"
#include "vtkObjectFactory.h"
#include "vtkOpenGLRenderer.h"
#include "vtkProperty.h"
#include "vtkSetGet.h"
#include "vtkShaderProgram.h"

#include "vtkStringToken.h"

#include "vtk_nlohmannjson.h"
#include VTK_NLOHMANN_JSON(json.hpp)

#include <fstream>
#include <sstream>
#include <string>
#include <vector>

#include <vtksys/SystemTools.hxx>

// Silence a warning that to_json() (generated by the invocation
// of NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE() below) is unused.
#if defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wunused-function"
#endif

VTK_ABI_NAMESPACE_BEGIN

namespace
{

//------------------------------------------------------------------------------
struct SubstitutionData
{
  std::string Target;
  std::string ShaderType;
  std::string FileName;
  bool ReplaceAllOccurrences = false;
  bool FileNameIsAbsolute = false;
  bool Enabled = true;
};

NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(SubstitutionData, Target, ShaderType, FileName,
  ReplaceAllOccurrences, FileNameIsAbsolute, Enabled)

//------------------------------------------------------------------------------
std::vector<SubstitutionData> ParseSubstitutions(
  vtkGLSLModPixelDebugger* mod, std::string& jsonFileName)
{
  using namespace nlohmann;
  json jsonObject;

  if (!vtksys::SystemTools::FileExists(jsonFileName))
  {
    // silently ignore files that do not exist.
    return {};
  }
  try
  {
    std::ifstream file(jsonFileName);
    jsonObject = json::parse(file);
  }
  catch (json::parse_error& ex)
  {
    vtkErrorWithObjectMacro(mod, << "Failed to parse " << jsonFileName << " with error \'"
                                 << ex.what() << "\' at byte " << ex.byte);
    return {};
  }

  if (!jsonObject.contains("Substitutions"))
  {
    vtkErrorWithObjectMacro(
      mod, << "Failed to find a list named \'Substitutions\'. Here's a sample json file "
              "that is valid: \n"
           << R"({
  "Substitutions": [
    {
      "Target": "//VTK::Light::Impl",
      "ShaderType": "Fragment",
      "FileName": "normal-debug.glsl",
      "ReplaceAllOccurrences": false,
      "FileNameIsAbsolute": false,
      "Enabled": false
    },
    {
      "Target": "//VTK::Light::Impl",
      "ShaderType": "Fragment",
      "FileName": "parametric-debug.glsl",
      "ReplaceAllOccurrences": false,
      "FileNameIsAbsolute": false,
      "Enabled": false
    }
  ]
}
)");
    return {};
  }

  auto substitutionObjects = jsonObject.at("Substitutions");
  int idx = -1;
  std::vector<SubstitutionData> substitutions;
  for (auto& substObject : substitutionObjects)
  {
    SubstitutionData substData = {};
    ++idx;
    try
    {
      substData = substObject.get<::SubstitutionData>();
    }
    catch (json::exception& ex)
    {
      vtkErrorWithObjectMacro(mod, << "Substitution Idx [" << idx << "] - Failed to deserialize \'"
                                   << substObject.dump() << "\' with error \'" << ex.what());
      continue;
    }
    substitutions.emplace_back(substData);
  }
  return substitutions;
}

//------------------------------------------------------------------------------
std::string GetGLSLFileContentsOnlyIfEnabled(
  const SubstitutionData& subst, const std::string& jsonFileName)
{
  auto path = vtksys::SystemTools::GetFilenamePath(jsonFileName);
  if (subst.Enabled)
  {
    std::string glslFileName = subst.FileName;
    // resolve path if not absolute.
    if (!subst.FileNameIsAbsolute)
    {
      glslFileName = vtksys::SystemTools::JoinPath({ path, "/", subst.FileName });
    }
    // read the glsl file contents
    std::ifstream file(glslFileName);
    std::stringstream contents;
    contents << file.rdbuf();
    return contents.str();
  }
  // disabled substitutions, treat it as if there is no glsl.
  return "";
}
}

//------------------------------------------------------------------------------
vtkStandardNewMacro(vtkGLSLModPixelDebugger);

//------------------------------------------------------------------------------
vtkGLSLModPixelDebugger::vtkGLSLModPixelDebugger()
{
  // load up a default based on source location.
  auto path = vtksys::SystemTools::GetFilenamePath(__FILE__);
  this->SubstitutionJSONFileName =
    vtksys::SystemTools::JoinPath({ path, "/", "LiveGLSLDebugSample", "sample.json" });

  if (!vtksys::SystemTools::FileExists(this->SubstitutionJSONFileName))
  {
    vtkDebugMacro(<< "Running without source repository."
                  << "The default json file " << this->SubstitutionJSONFileName
                  << " does not exist. ");
  }
}

//------------------------------------------------------------------------------
vtkGLSLModPixelDebugger::~vtkGLSLModPixelDebugger() = default;

//------------------------------------------------------------------------------
void vtkGLSLModPixelDebugger::PrintSelf(ostream& os, vtkIndent indent)
{
  os << "SubstitutionJSONFileName: " << this->SubstitutionJSONFileName;
  this->Superclass::PrintSelf(os, indent);
}

//------------------------------------------------------------------------------
bool vtkGLSLModPixelDebugger::ReplaceShaderValues(vtkOpenGLRenderer* vtkNotUsed(renderer),
  std::string& vertexShader, std::string& geometryShader, std::string& fragmentShader,
  vtkAbstractMapper* vtkNotUsed(mapper), vtkActor* vtkNotUsed(actor))
{
  auto substs = ::ParseSubstitutions(this, this->SubstitutionJSONFileName);
  int idx = -1;
  for (const auto& subst : substs)
  {
    auto glslContents = ::GetGLSLFileContentsOnlyIfEnabled(subst, this->SubstitutionJSONFileName);
    ++idx;
    if (glslContents.empty())
    {
      if (subst.Enabled)
      {
        // warn if user made a mistake in the filename or gave no glsl code.
        vtkWarningMacro(<< "Substitutions[" << idx
                        << "] was enabled but glsl code was empty! Maybe incorrect filename?");
      }
      continue;
    }
    if (subst.ShaderType == "Fragment")
    {
      vtkShaderProgram::Substitute(
        fragmentShader, subst.Target, glslContents, subst.ReplaceAllOccurrences);
    }
    else if (subst.ShaderType == "Geometry")
    {
      vtkShaderProgram::Substitute(
        geometryShader, subst.Target, glslContents, subst.ReplaceAllOccurrences);
    }
    else if (subst.ShaderType == "Vertex")
    {
      vtkShaderProgram::Substitute(
        vertexShader, subst.Target, glslContents, subst.ReplaceAllOccurrences);
    }
  }
  // std::cout << "VS:" << vertexShader << std::endl;
  // std::cout << "GS:" << geometryShader << std::endl;
  // std::cout << "FS:" << fragmentShader << std::endl;
  this->LastSubstitutionJSONFileContentsToken = this->HashSubstitutionJSONFileContents();
  this->LastGLSLFilesContentsToken = this->HashGLSLFilesContents();
  return true;
}

//------------------------------------------------------------------------------
bool vtkGLSLModPixelDebugger::SetShaderParameters(vtkOpenGLRenderer* vtkNotUsed(renderer),
  vtkShaderProgram* vtkNotUsed(program), vtkAbstractMapper* vtkNotUsed(mapper),
  vtkActor* vtkNotUsed(actor), vtkOpenGLVertexArrayObject* vtkNotUsed(VAO) /*=nullptr*/)
{
  return true;
}

//------------------------------------------------------------------------------
vtkStringToken vtkGLSLModPixelDebugger::HashSubstitutionJSONFileContents()
{
  std::ifstream file(this->SubstitutionJSONFileName);
  std::stringstream contents;
  contents << file.rdbuf();
  return contents.str();
}

//------------------------------------------------------------------------------
vtkStringToken vtkGLSLModPixelDebugger::HashGLSLFilesContents()
{
  auto substs = ::ParseSubstitutions(this, this->SubstitutionJSONFileName);
  std::string glslContents;
  for (const auto& subst : substs)
  {
    glslContents += ::GetGLSLFileContentsOnlyIfEnabled(subst, this->SubstitutionJSONFileName);
  }
  return glslContents;
}

VTK_ABI_NAMESPACE_END
